package com.bluexmicro.module_componment.scan

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanResult
import android.os.SystemClock
import android.text.TextUtils
import android.util.SparseArray
import java.nio.ByteBuffer
import java.util.concurrent.atomic.AtomicBoolean

@SuppressLint("MissingPermission")
data class BleDevice(
    val scanResult: ScanResult
) {

    val name: String = scanResult.device.name ?: "N/A"
    val mac: String = scanResult.device.address

    val advData: ByteArray? = scanResult.scanRecord?.bytes
    val rssi = scanResult.rssi
    val device: BluetoothDevice = scanResult.device
    val timeMills: Long = timeMillis(scanResult)
    var connectable = false
        private set
    var manufacturerData: ByteArray? = null
        private set
    var mesh = false
        private set
    val dataTypeArray = SparseArray<ByteArray>()
    var localName: String? = null
        private set
    private val DATA_TYPE_FLAGS: Byte = 0x01
    private val DATA_TYPE_LOCAL_NAME_COMPLETE: Byte = 0x09
    private val DATA_TYPE_MESH_ADV: Byte = 0x2B
    private val DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: Byte = 0x03
    private val DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF.toByte()


    private fun setupAdvertise(advData: ByteArray) {
        val buffer = ByteBuffer.allocate(advData.size)
        buffer.put(advData)
        buffer.rewind()
        while (buffer.position() < buffer.capacity()) {
            val len: Int = buffer.get().toUInt8()
            if (buffer.position() + len <= buffer.capacity()) {
                val bytes = ByteArray(len)
                buffer[bytes]
                parseAdvParams(bytes)
            }
        }
    }

    private fun parseAdvParams(bytes: ByteArray) {
        if (bytes.isEmpty()) {
            return
        }
        val type = bytes[0]
        val content = ByteArray(bytes.size - 1)
        val key = type.toUInt8()
        dataTypeArray.put(key, content)
        System.arraycopy(bytes, 1, content, 0, content.size)
        when (type) {
            DATA_TYPE_FLAGS -> this.connectable = true
            DATA_TYPE_MANUFACTURER_SPECIFIC_DATA -> manufacturerData = content
            DATA_TYPE_LOCAL_NAME_COMPLETE -> localName = String(content)
            DATA_TYPE_MESH_ADV ->                 //adv承载器 （广播）
                this.mesh = true
            DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE -> {
                val uuid16bit: Int = toUInt16(content[0], content[1])
                //gatt 承载器（支持连接）
                mesh = uuid16bit == 0x2818 || uuid16bit == 0x2718
            }
            else -> {}
        }
    }

    private fun timeMillis(scanResult: ScanResult): Long {
        return System.currentTimeMillis() -
                SystemClock.elapsedRealtime() +
                scanResult.timestampNanos / 1000000
    }

    fun getDisplayName(): String {
        return if (!TextUtils.isEmpty(localName)) {
            localName!!
        } else if (!TextUtils.isEmpty(name)) {
            name
        } else {
            "N/A"
        }
    }

    override fun toString(): String {
        return "BleDevice(name='$name', mac='$mac', rssi=$rssi)"
    }


    init {
        advData?.let {
            setupAdvertise(it)
        }
    }
}

fun Byte.toUInt8(): Int {
    return this.toInt().and(0xff)
}

fun toUInt16(high: Byte, low: Byte): Int {
    return (high.toUInt8() shl 8).or(low.toUInt8())
}

class FilterModel(
    val rssiThreshold: Int = -127,
    val connectable: Boolean = true,
    val flag: String? = null, //name or mac
    val configuring: Boolean = false
)

class ConditionModel {
    var bluetoothEnable = false
    var locationEnable = false
    var locationPermission = false
    var bluetoothScanPermission = false
    var bluetoothConnectPermission = false

    fun isSame(model: ConditionModel): Boolean {
        if (bluetoothEnable != model.bluetoothEnable) return false
        if (locationEnable != model.locationEnable) return false
        if (locationPermission != model.locationPermission) return false
        if (bluetoothScanPermission != model.bluetoothScanPermission) return false
        if (bluetoothConnectPermission != model.bluetoothConnectPermission) return false
        return true
    }

    fun isScanReady(): Boolean {
        return bluetoothEnable && locationEnable && bluetoothScanPermission && locationPermission && bluetoothConnectPermission
    }

    fun clone(): ConditionModel {
        return ConditionModel().also {
            it.bluetoothEnable = bluetoothEnable
            it.locationEnable = locationEnable
            it.locationPermission = locationPermission
            it.bluetoothScanPermission = bluetoothScanPermission
            it.bluetoothConnectPermission = bluetoothConnectPermission
        }
    }
}

class DeviceModel {

    private val deviceCache = mutableMapOf<String, BleDevice>()
    private var sortedAddresses = mutableListOf<String>()
    private val updating = AtomicBoolean(false)

    fun append(scanResults: List<ScanResult>, filters: FilterModel) {
        if (updating.compareAndSet(false, true)) {
            for (scanResult in scanResults) {
                val device = BleDevice(scanResult)
                if (!sortedAddresses.contains(device.mac) && device.isTarget(filters)) {
                    sortedAddresses.add(device.mac)
                }
                deviceCache[device.mac] = device
            }
            updating.set(false)
        }
    }

    fun filterAndSort(filters: FilterModel) {
        if (updating.compareAndSet(false, true)) {
            val tmp = deviceCache.values.toList()
            deviceCache.clear()
            for (bleDevice in tmp) {
                if (bleDevice.isTarget(filters)) {
                    deviceCache[bleDevice.mac] = bleDevice
                }
            }
            sortedAddresses = deviceCache.values.sortedWith(Comparator { o1, o2 ->
                if (o1.rssi > o2.rssi) {
                    -1
                } else if (o1.rssi < o2.rssi) {
                    1
                } else 0
            }).map {
                it.mac
            }.toMutableList()
//            sortedAddresses = deviceCache.values.sortedBy {
//                it.rssi
//            }.map {
//                it.mac
//            }.toMutableList()
            updating.set(false)
        }
    }

    fun getDisplayDevices(): List<BleDevice> {
        val list = mutableListOf<BleDevice>()
        for (address in sortedAddresses) {
            deviceCache[address]?.apply {
                list.add(this)
            }
        }
        return list
    }

    private fun BleDevice.isTarget(filters: FilterModel): Boolean {
        val flag = filters.flag?.let {
            if (it.isEmpty()) {
                true
            } else if (name.contains(it, true)) {
                true
            } else if (localName?.contains(it, true) == true) {
                true
            } else mac.contains(it, true)
        } ?: true

        val rssi = rssi >= filters.rssiThreshold
        val connectable = if (filters.connectable) connectable else true
        return connectable && rssi && flag
    }

}